package com.allo2o.common.biz.sequence.mybatis;

import org.apache.ibatis.executor.BatchExecutor;
import org.springframework.beans.factory.annotation.Autowired;

import com.allo2o.common.biz.sequence.exception.SequenceException;
import com.allo2o.common.biz.sequence.longimpl.LongSequenceRange;
import com.allo2o.common.biz.sequence.longimpl.LongSequenceRangeGenerator;
import com.allo2o.common.domain.SequenceDO;
import com.allo2o.common.service.ISequenceService;

public class MybatisSequenceRangeGenerator extends LongSequenceRangeGenerator {

    /**
     * 
     */
    private static final long serialVersionUID = 3412630991154921876L;

    @Autowired
    private ISequenceService  sequenceService;

    @Override
    public LongSequenceRange nextRange(String name) throws SequenceException {
        if (name == null) {
            throw new IllegalArgumentException("Sequence name must be not null");
        }
        int step;
        int retryTimes;
        SequenceDO sequenceDO;
        long oldValue = 0L;
        long newValue = 0L;
        int loopTimes = 0;
        do {
            loopTimes++;
            sequenceDO = sequenceService.selectBySequenceName(name);
            oldValue = sequenceDO.getCurrentValue();
            StringBuilder message;
            if (oldValue < 0L) {
                message = new StringBuilder();
                message.append("Sequence value cannot be less than zero, value = ").append(oldValue);
                message.append(", please check sequenceName ").append(name);
                throw new SequenceException(message.toString());
            }
            if (oldValue > Long.MAX_VALUE) {
                message = new StringBuilder();
                message.append("Sequence value overflow, value = ").append(oldValue);
                message.append(", please check sequenceName ").append(name);
                throw new SequenceException(message.toString());
            }
            step = sequenceDO.getStepSize();
            if ((step < MIN_STEP) || (step > MAX_STEP)) {
                message = new StringBuilder();
                message.append("step out of range [").append(MIN_STEP);
                message.append(",").append(MAX_STEP).append("], step = ").append(step);
                message.append(", will use default step = ").append(DEFAULT_STEP);
                step = DEFAULT_STEP;
                LOGGER.warn(message);
            }
            newValue = oldValue + step;
            SequenceDO sequenceDOUpdate = new SequenceDO();
            sequenceDOUpdate.setSequenceId(sequenceDO.getSequenceId());
            sequenceDOUpdate.setCurrentValue(newValue);
            sequenceDOUpdate.setGmtModified(sequenceDO.getGmtModified());
            int affectedRows = sequenceService.updateByOldValue(sequenceDOUpdate, oldValue);
            if (affectedRows > 0 || BatchExecutor.BATCH_UPDATE_RETURN_VALUE == affectedRows) {
                LongSequenceRange longSequenceRange = new LongSequenceRange(oldValue + 1L, newValue);
                return longSequenceRange;
            } else {
                if (sequenceDO.getRetry()) {
                    retryTimes = sequenceDO.getRetryTimes();
                    if (retryTimes > DEFAULT_RETRY_TIMES) {
                        message = new StringBuilder();
                        message.append("retryTimes > ");
                        message.append(DEFAULT_RETRY_TIMES);
                        message.append(" at sequenceId = " + sequenceDO.getSequenceId());
                        message.append(", will use default retryTimes = ").append(DEFAULT_RETRY_TIMES);
                        step = DEFAULT_RETRY_TIMES;
                        LOGGER.warn(message);
                    }
                } else {
                    retryTimes = loopTimes - 1;
                }
            }
        } while (retryTimes > loopTimes);
        throw new SequenceException("Retried too many times, retryTimes = " + retryTimes);
    }
}
